Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Form validation kata #2

Merged
merged 3 commits into from
Mar 31, 2019
Merged

Form validation kata #2

merged 3 commits into from
Mar 31, 2019

Conversation

pedrovgs
Copy link
Owner

@pedrovgs pedrovgs commented Mar 31, 2019

🎩 What is the goal?

To solve a simple exercise using Validated data type. The exercise statement can be found here. In this exercise, the main goal is to validate a form returning all the errors if there are some.

How is it being implemented?

To implement this Kata we decided to use Refined types in Scala. However, the usage of these types guarantees that the Form type should be instantiated in a valid way using literals or should be instantiated using refinedV where the possible validation error should be handled before instantiating any field. That's why we created the type UnsafeForm. As most of the time our values are generated in runtime we needed a type to hold all this information and then a FormValidator we implemented easily thanks to these functions:

  • mapN. Letting us transform a Tuple6 into a ValidatedNel<FormError, Form>. You can see here how the usage of implicits let us don't indicate the NonEmptyList semigroup instance or the applicative instance we needed in other solutions like the Arrow one written in Kotlin
  • The definition of the types using refined:
type FirstName       = Refined[String, NonEmpty]
  type LastName        = Refined[String, NonEmpty]
  type ValidDocumentId = MatchesRegex[W.`"""\\d{8}[a-zA-Z]{1}"""`.T]
  type DocumentId      = Refined[String, ValidDocumentId]
  type ValidPhone      = MatchesRegex[W.`"""\\d{9}"""`.T]
  type Phone           = Refined[String, ValidPhone]
  type Email           = Refined[String, ValidEmail]

  case class ValidEmail()
  implicit val emailValidate: Validate.Plain[String, ValidEmail] =
    Validate.fromPredicate(e => e.contains("@"), p => s"$p is not a valid email", ValidEmail())

We created our own type named ValidEmail and used the already implemented ones to compose the rest of the types. The usage of regex was really interesting combined with the type definition.

  • Validated.fromEither. Letting us transform the Either generated by refineV into a Validated as follows:
 Validated
      .fromEither(refineV[NonEmpty](firstName))
      .leftMap(_ => NonEmptyList.of(EmptyFirstName(firstName)))

Last but not least, there is an interesting point when taking a look at the test coverage. We couldn't get the generators working for the types based on MatchesRegex type. I guess this is because it's not possible to generate values matching the expression in a reasonable time. So in this scenario, we couldn't automatically derive our generators as we did in the previous kata 😢

If you are wondering why we used Validated instead of Either this blog post will resolve your question: https://www.tobyhobson.com/cats-validated/

How can it be tested?

Automatically! 🤖 I've added some automated tests I'd recommend you to review 😃

@pedrovgs pedrovgs merged commit 792c792 into master Mar 31, 2019
@pedrovgs pedrovgs deleted the form-validation-kata branch March 31, 2019 19:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant